package edu.kit.aifb.ldstream.learner.tasks;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.openrdf.model.BNode;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.ParserConfig;
import org.openrdf.rio.RDFParser.DatatypeHandling;

import edu.kit.aifb.ldstream.learner.dao.Instance;
import edu.kit.aifb.ldstream.learner.dao.Type;
import edu.kit.aifb.ldstream.learner.util.RecursiveSum;

public class StructurednessTask extends LearnerTask{

	private Map<Type, ArrayList<Instance>> classMap;
	private Coherence ch;
	private boolean inferencing;

	public StructurednessTask(Map<Type, ArrayList<Instance>> classMap, Coherence ch, boolean inferencing){
		this.classMap = classMap;
		this.ch = ch;
		this.inferencing = inferencing;
	}


	@Override
	public void run() {
		// prolog
		startup();
		setPreserveBnode();

		int totalNumProps = computeCoverage();
		computeCoverageWeigth(totalNumProps);
		computeCoherence();

		shutdown();
	}
	private void computeCoherence(){
		// final step coherence = structuredness
		double ch = 0;
		List<Double> chList = new LinkedList<Double>();
		for(Type t : this.classMap.keySet()){
			if(!this.classMap.get(t).isEmpty()){
				// ch = sum(wt(t) * cv(t))
				//ch += t.getCoverageWeight() * t.getCoverage();
				chList.add(t.getCoverageWeight() * t.getCoverage());
			}
		}
		// TODO could be unnecessary overhead... needs some tests
		double[] chArray = new double[chList.size()];
		int i = 0;
		for(double d : chList){
			chArray[i++] = d;  
		}
		ch = RecursiveSum.recursiveSum(chArray);
		
		this.ch.setCoherence(ch);
	}

	private void computeCoverageWeigth(int totalNumProps){
		int totalNumInstances = getTotalNumInstances();
		double wt;
		double denominator = totalNumProps + totalNumInstances;

		for(Type t : this.classMap.keySet()){
			if(!this.classMap.get(t).isEmpty()){
				wt = (this.classMap.get(t).size() + t.getNumProps()) / denominator;
				t.setCoverageWeight(wt);
			}
		}
	}

	private int getTotalNumInstances(){
		int num = 0;
		for(Type t : this.classMap.keySet()){
			// calculating the total instances count
			num += this.classMap.get(t).size();
		}
		return num;
	}

	private int computeCoverage(){
		// the number of all properties is needed for the weight computation
		int totalNumProps = 0;
		for(Type t : this.classMap.keySet()){
			// only crawl classes with instances
			if(!this.classMap.get(t).isEmpty()){
				Map<URI, HashSet<URI>> coverageMap = new HashMap<URI, HashSet<URI>>();
				for(Instance instance : this.classMap.get(t)){

					RepositoryResult<Statement> result = null;
					Statement s = null;
					URI subj, pred;
					Value obj;
					try {
						result = this.con.getStatements(instance.getURI(), null, null, this.inferencing);
						try{
							while(result.hasNext()){
								s = result.next();
								pred = s.getPredicate();
								subj = (URI) s.getSubject();
								obj = s.getObject();
								if(!coverageMap.containsKey(pred)){
									coverageMap.put(pred, new HashSet<URI>());
								}
								coverageMap.get(pred).add(subj);
								if(obj instanceof BNode){
									computeCoverage((BNode)obj, subj, coverageMap);
								}
							}
						}finally{
							result.close();
						}
					} catch (RepositoryException e) {
						e.printStackTrace();
					}
				}
				// every instance is crawled
				/*
				 * Coverage formula:
				 * sum( occurrences( property, instance) ) / numberProp(type) * numInstances(type)
				 */
				int oc = 0;
				for(URI prop : coverageMap.keySet()){
					// getting the number of instances which have set the property "prop"
					oc += coverageMap.get(prop).size();
				}
				// numProp(type) = keyset.size
				// numInstances(type) ) classmap.get(type).size
				double cv = ((double) oc) / (coverageMap.size() * this.classMap.get(t).size());
				// saving the coverage of that type
				t.setCoverage(cv);
				// setting the number of properties for type ( needed for the coverage weight)
				t.setNumProperties(coverageMap.size());
				// getting the total number of properties (over all types)
				totalNumProps += coverageMap.size();
			}
		}
		return totalNumProps;
	}

	private void computeCoverage(BNode b, URI instance, Map<URI, HashSet<URI>> coverageSet){

		RepositoryResult<Statement> result = null;
		Statement s = null;
		URI pred;
		Value obj;
		try {
			result = this.con.getStatements(b, null, null, this.inferencing);
			try{
				while(result.hasNext()){
					s = result.next();
					pred = s.getPredicate();
					obj = s.getObject();
					if(!coverageSet.containsKey(pred)){
						coverageSet.put(pred, new HashSet<URI>());
					}
					coverageSet.get(pred).add(instance);
					if(obj instanceof BNode){
						computeCoverage((BNode)obj, instance, coverageSet);
					}
				}
			}finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
	}

	private void setPreserveBnode(){
		this.con.setParserConfig(new ParserConfig(true, true, true, DatatypeHandling.VERIFY));
	}
}
